package com.oocl.scan.handler;

import com.oocl.scan.jdt.parser.ModifierParser;
import com.oocl.scan.model.ClassField;
import com.oocl.scan.model.Column;
import com.oocl.scan.model.Component;
import com.oocl.scan.model.JavaClassElement;
import com.oocl.scan.model.JoinColumn;
import com.oocl.scan.model.ModelResult;
import com.oocl.scan.model.Relationship;
import com.oocl.scan.utils.ScanConstants;
import java.util.ArrayList;
import java.util.List;
import org.eclipse.jdt.core.dom.Expression;
import org.eclipse.jdt.core.dom.FieldDeclaration;
import org.eclipse.jdt.core.dom.NormalAnnotation;
import org.eclipse.jdt.core.dom.ParameterizedType;
import org.eclipse.jdt.core.dom.SimpleType;
import org.eclipse.jdt.core.dom.SingleMemberAnnotation;
import org.eclipse.jdt.core.dom.Type;
import org.eclipse.jdt.core.dom.TypeDeclaration;
import org.eclipse.jdt.core.dom.VariableDeclarationFragment;


/**
 * Created by 矩阵 on 2018/4/1.
 */
public class ModelHandler extends BaseHandler{

  @Override
  public void handle(JavaClassElement javaClassElement) {
    JavaClassElement element = javaClassElement;
    TypeDeclaration typeDeclaration = javaClassElement.getTypeDeclaration();
    NormalAnnotation tableAnnotation = ModifierParser.getAnnotation(typeDeclaration, ModelResult.TABLE);
    NormalAnnotation entityAnnotation = ModifierParser.getAnnotation(typeDeclaration, ModelResult.ENTITY);
    if (tableAnnotation != null && entityAnnotation != null) {
      SingleMemberAnnotation accessAnnotation = ModifierParser.getSingleMemberAnnotation(typeDeclaration, ModelResult.ACCESS);
      ModelResult modelResult = new ModelResult();
      modelResult.setName(javaClassElement.getPackageDeclaration().getName() + "." + javaClassElement.getClassName());
      modelResult.setEntity(
          ModifierParser.getAnnotationStringValueByName(entityAnnotation, "name"));
      modelResult.setTable(
          ModifierParser.getAnnotationStringValueByName(tableAnnotation, "name"));
      modelResult.setAccess((accessAnnotation != null ? accessAnnotation.getValue().toString().split("\\.") : new String[0])[1]);
      modelResult.setDomain(javaClassElement.getDomainName());
      modelResult.setModuleName(javaClassElement.getModuleName());
      List<ClassField> classFieldList = new ArrayList<>();
      List<FieldDeclaration> fieldDeclarationList = javaClassElement.getFieldDeclarationList();
      for (FieldDeclaration fieldDeclaration : fieldDeclarationList) {
        ClassField classField = getClassField(fieldDeclaration, element.getPackageDeclaration().getName().getFullyQualifiedName(), element);
        if (classField != null) {
          classFieldList.add(classField);
        }
      }
      modelResult.setClassFieldList(classFieldList);
      buildModelComponent(modelResult);
    }
  }

  private void buildModelComponent(ModelResult modelResult) {
    Component model = getModelComponent(modelResult);
    super.addToComponentStore(model);
    for (ClassField classField : modelResult.getClassFieldList()) {
      if (!isPrimitive(classField.getType())) {
        Relationship relationship = new Relationship();
        relationship.setType(ScanConstants.RELATION_TYPE_IN);
        relationship.setFrom(classField.getType());
        relationship.setFromModule(model.getModule());
        relationship.setTo(model.getName());
        relationship.setToModule(model.getModule());
        addToRelationshipStore(relationship);
      }
    }
  }

  private Component getFieldColumnComponent(ModelResult modelResult, ClassField classField) {
    Component fieldColumn = new Component();
    fieldColumn.setDomain(modelResult.getDomain());
    fieldColumn.setModule(modelResult.getModuleName());
    fieldColumn.setType(ScanConstants.FIELD_COLUMN);
    String name = modelResult.getName();
    fieldColumn.setDescription(classField.getType());
    if (classField.getColumn() != null) {
      name += "-{" + classField.getName() + "}[" + classField.getColumn().getName() + "]";
      fieldColumn.setName(name);
      return fieldColumn;
    }
    if (classField.getJoinColumn() != null) {
      name += "-{" + classField.getName() + "}[" + classField.getJoinColumn().getName() + "]";
      fieldColumn.setName(name);
      return fieldColumn;
    }
    return null;
  }

  private Component getModelComponent(ModelResult modelResult) {
    Component model = new Component();
    model.setDomain(modelResult.getDomain());
    model.setName(modelResult.getName());
    model.setModule(modelResult.getModuleName());
    model.setType(ScanConstants.MODEL_TYPE);
    ModelDescription modelDescription = new ModelDescription();
    modelDescription.setTable(modelResult.getTable());
    modelDescription.setClassFieldList(modelResult.getClassFieldList());
    model.setDescription(modelDescription);
    return model;
  }

  class ModelDescription {

    private String table;
    private List<ClassField> classFieldList;

    public String getTable() {
      return table;
    }

    public void setTable(String table) {
      this.table = table;
    }

    public List<ClassField> getClassFieldList() {
      return classFieldList;
    }

    public void setClassFieldList(List<ClassField> classFieldList) {
      this.classFieldList = classFieldList;
    }
  }


  private String getJoinColumnReference(FieldDeclaration fieldDeclaration) {
    NormalAnnotation reference = ModifierParser.getAnnotation(fieldDeclaration, ClassField.ONE_TO_ONE);
    if (reference != null) {
      return ClassField.ONE_TO_ONE;
    }
    reference = ModifierParser.getAnnotation(fieldDeclaration, ClassField.ONE_TO_MANY);
    if (reference != null) {
      return ClassField.ONE_TO_MANY;
    }
    reference = ModifierParser.getAnnotation(fieldDeclaration, ClassField.MANY_TO_MANY);
    if (reference != null) {
      return ClassField.MANY_TO_MANY;
    }
    reference = ModifierParser.getAnnotation(fieldDeclaration, ClassField.MANY_TO_ONE);
    if (reference != null) {
      return ClassField.MANY_TO_ONE;
    }
    return null;
  }

  private ClassField getClassField(FieldDeclaration fieldDeclaration, String packageName, JavaClassElement element) {
    if (ModifierParser.isFinal(fieldDeclaration) && ModifierParser.isStatic(fieldDeclaration)) {
      return null;
    }
    ClassField classField = new ClassField();
    String name = "";
    for (Object obj : fieldDeclaration.fragments()) {
      VariableDeclarationFragment frag = (VariableDeclarationFragment) obj;
      name = frag.getName().getFullyQualifiedName();
    }
    classField.setName(name);
    Type type = fieldDeclaration.getType();
    String className = "";
    if (type instanceof ParameterizedType) {
      ParameterizedType parameterizedType = (ParameterizedType) type;
      Type argumentType = (Type) parameterizedType.typeArguments().get(0);

      String ReferencedModel = null;
      if (isPrimitive(argumentType.toString())) {
        ReferencedModel = argumentType.toString();
      }
      if (argumentType instanceof SimpleType) {
        className = ((SimpleType) argumentType).getName().getFullyQualifiedName();
      }
      classField.setReferencedModel(ReferencedModel);
    } else {
      className = fieldDeclaration.getType().toString();
    }

    String fullClassName = className;
    if (!isPrimitive(className)) {
      fullClassName = element.findPackageNameByClass(className);
    }
    if (!fullClassName.contains(".")) {
      fullClassName = packageName + "." + fullClassName;
    }

    classField.setType(fullClassName);
    NormalAnnotation columnAnnotation = ModifierParser.getAnnotation(fieldDeclaration, ClassField.COLUMN);
    if (columnAnnotation != null) {
      Column column = new Column();
      column.setName(ModifierParser.getAnnotationStringValueByName(columnAnnotation, "name"));
      if (ModifierParser.getAnnotationValueByName(columnAnnotation, "nullable") != null) {
        column.setNullable(
            ModifierParser.getAnnotationStringValueByName(columnAnnotation, "nullable"));
      }
      classField.setColumn(column);
    }
    NormalAnnotation joinTableAnnotation = ModifierParser
        .getAnnotation(fieldDeclaration, ClassField.JOIN_TABLE);
    if (joinTableAnnotation != null) {
      Expression joinColumnsExpress = ModifierParser
          .getAnnotationValueByName(joinTableAnnotation, "joinColumns");
      if (joinColumnsExpress instanceof NormalAnnotation) {
        NormalAnnotation joinColumnAnnotation = (NormalAnnotation) joinColumnsExpress;
        JoinColumn joinColumn = getJoinColumn(fieldDeclaration, joinColumnAnnotation);
        classField.setJoinColumn(joinColumn);
      }
    }
    NormalAnnotation joinColumnAnnotation = ModifierParser
        .getAnnotation(fieldDeclaration, "JoinColumn");
    if (joinColumnAnnotation != null) {
      JoinColumn joinColumn = getJoinColumn(fieldDeclaration, joinColumnAnnotation);
      classField.setJoinColumn(joinColumn);
    }

    return classField;
  }


  private JoinColumn getJoinColumn(FieldDeclaration fieldDeclaration, NormalAnnotation joinColumnAnnotation) {
    JoinColumn joinColumn = new JoinColumn();
    joinColumn.setName(ModifierParser.getAnnotationStringValueByName(joinColumnAnnotation, "name"));
    if (ModifierParser.getAnnotationValueByName(joinColumnAnnotation, "nullable") != null) {
      joinColumn.setNullable(
          ModifierParser.getAnnotationStringValueByName(joinColumnAnnotation, "nullable"));
    }
    joinColumn.setReference(getJoinColumnReference(fieldDeclaration));
    if (ModifierParser.getAnnotationValueByName(joinColumnAnnotation, "referencedColumnName") != null) {
      joinColumn.setReferencedColumnName(ModifierParser
          .getAnnotationStringValueByName(joinColumnAnnotation, "referencedColumnName"));
    }
    return joinColumn;
  }

  private boolean isPrimitive(String clazz) {
    List<String> primitiveList = new ArrayList<>();
    primitiveList.add("int");
    primitiveList.add("Integer");
    primitiveList.add("byte");
    primitiveList.add("Byte");
    primitiveList.add("short");
    primitiveList.add("Short");
    primitiveList.add("long");
    primitiveList.add("Long");
    primitiveList.add("float");
    primitiveList.add("Float");
    primitiveList.add("double");
    primitiveList.add("Double");
    primitiveList.add("boolean");
    primitiveList.add("Boolean");
    primitiveList.add("char");
    primitiveList.add("Char");
    primitiveList.add("String");
    return primitiveList.contains(clazz);
  }
}
